<?php

#
# If X-SKIP-SAML header is present, disallow anonymous viewing and editing and
# skip SAML login. Allow standard MediaWiki login.
#
if ( isset( $_SERVER['HTTP_X_SKIP_SAML'] ) ) {

	# Disable reading, editing and account creation by anonymous users
	$wgGroupPermissions['*']['read'] = false;
	$wgGroupPermissions['*']['edit'] = false;
	$wgGroupPermissions['*']['createaccount'] = false;
	# Note: account creation disabled for everyone at bottom of this file

	# Disable API modules that could allow someone to circumvent SAML auth
	# ref: https://www.mediawiki.org/wiki/API:Restricting_API_usage
	# ref: https://www.mediawiki.org/wiki/API:Changing_wiki_content
	$wgAPIModules['createaccount'] = 'ApiDisabled';
	$wgAPIModules['options'] = 'ApiDisabled';
	$wgAPIModules['resetpassword'] = 'ApiDisabled';

	// A bit heavy-handed:
	// $wgEnableWriteAPI = false;

	// Add to config like:
	//
	// allow_skip_saml_users:
	//   - __allusers: ["*"]
	//   - James: ["192.168.56.62","23.243.32.123"]
	//   - Daren: ["*"]
	//   - Lauren:
	//     - "192.54.43.13"
	//     - "3.3.2.1"
	//   - Willa:
	//     - "192.169.34.23"
	//
	// FIXME #822: The indenting below will be heinous when Ansible does its templating
	{% if allow_skip_saml_users is defined -%}
		$wgMezaAllowSkipSamlUsers = array();
		{% for user, ipaddrs in allow_skip_saml_users.items() -%}
		$wgMezaAllowSkipSamlUsers['{{ user }}'] = array(
			{%- for ipaddr in ipaddrs -%}'{{ ipaddr }}',{%- endfor -%}
		);
		{%- endfor %}
	{% else -%}
		$wgMezaAllowSkipSamlUsers = false;
	{%- endif %}


	// Upon login if user is allowed to skip SAML (and therefore use the API
	// externally)
	$wgHooks['UserLoggedIn'][] = function ( $user ) {
		global $wgMezaAllowSkipSamlUsers;

		$username = $user->getName();
		$ipaddr = $_SERVER['HTTP_X_FORWARDED_FOR'];

		$forbidden = function ($msg='') {
			// lightly copied from MediaWiki's img_auth.php
			HttpStatus::header( 403 );
			header( 'Cache-Control: no-cache' );
			header( 'Content-Type: text/html; charset=utf-8' );
			echo "You do not have rights to access this wiki in this way.";
			die($msg);
		};

		if ( ! is_array( $wgMezaAllowSkipSamlUsers ) ) {
			$forbidden();
			return false; // pointless due to die() in forbidden
		}

		// allow option to greenlight all users
		if ( isset( $wgMezaAllowSkipSamlUsers['__allusers'] ) ) {

			// if * users can access via * ip addresses
			if ( $wgMezaAllowSkipSamlUsers['__allusers'][0] === '*' ) {
				return true;
			}

			// if * users can access via the current IP address
			elseif ( in_array( $ipaddr, $wgMezaAllowSkipSamlUsers['__allusers'] ) ) {
				return true;
			}
		}

		if ( isset( $wgMezaAllowSkipSamlUsers[$username] ) ) {

			// user allowed from all IP addresses
			if ( $wgMezaAllowSkipSamlUsers[$username][0] === '*' ) {
				return true;
			}

			$IPv4matchesCIDR = function( $ip, $cidr) {
				$arr = explode( '/', $cidr );
				if ( count($arr) === 1 ) {
					$subnet = $arr[0];
					$bits = 32; // Allows passing in CIDR or maskless IP
				}
				else {
					list ( $subnet, $bits) = $arr;
				}
				$ip = ip2long( $ip );
				$subnet = ip2long( $subnet );
				$mask = -1 << (32 - $bits);
				$subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned

				return ( $ip & $mask ) == $subnet;
			};

			$IPv6matchesCIDR = function( $ip, $cidr) {
				$inet = inet_pton( $ip );

				// converts inet_pton output to string with bits
				$unpacked = unpack( 'A16', $inet );
				$unpacked = str_split( $unpacked[1] );
				$binaryip = '';
				foreach ( $unpacked as $char ) {
					$binaryip .= str_pad( decbin( ord( $char ) ), 8, '0', STR_PAD_LEFT );
				}

				$arr = explode( '/', $cidr );
				if ( count($arr) === 1 ) {
				$subnet = $arr[0];
				$bits = 128; // Allows passing in CIDR or maskless IP
				}
				else {
					list ( $subnet, $bits ) = $arr;
				}

				// Convert subnet to binary string
				$subnet = inet_pton( $subnet );
				$binarynet = inet_to_bits( $subnet );

				$ip_net_bits = substr( $binaryip, 0, $bits );
				$net_bits = substr( $binarynet, 0, $bits );

				return $ip_net_bits === $net_bits;
			};

			$addressesToCheck = $wgMezaAllowSkipSamlUsers[$username];

			// Loop through all addresses, returning true on first where client's IP matches
			// Otherwise, fall through to $forbidden()
			foreach( $addressesToCheck as $check ) {
				if (
					filter_var( $ipaddr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) &&
					$IPv4matchesCIDR( $ipaddr, $check )
				) {
					return true;
				}

				elseif (
					defined( 'AF_INET6' ) &&
					filter_var( $ipaddr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) &&
					$IPv6matchesCIDR( $ipaddr, $check )
				) {
					return true;
				}
			}
		}

		$forbidden();
		return false; // pointless due to die() in forbidden
	};
}

#
# Extension:SimpleSamlAuth
#
# Only do auth on requests from outside the server. Requests from inside are a
# service...in the past, Parsoid, but that's not required MW 1.35+. Requests
# from outside will come from HAProxy and
# therefore will have an HTTP_X_FORWARDED_FOR variable
#
elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {

	wfLoadExtension( 'PluggableAuth' );
	wfLoadExtension( 'SimpleSAMLphp' );

	// the base SAML config variables exist in this file, such that it's
	// easy for the landing page to use them, too.
	require_once "{{ m_deploy }}/SAMLConfig.php";

	// Array: [MediaWiki group][SAML attribute name][SAML expected value]
	// If the SAML assertion matches, the user is added to the MediaWiki group
	$wgSamlGroupMap = array(
		//'sysop' => array(
		//	'groups' => array('admin'),
		//),
	);

	{# $GLOBALS['wgHooks']['SpecialPage_initList'][] = function (&$list) {
		unset( $list['Userlogout'] );
		unset( $list['Userlogin'] );
		return true;
	};

	$GLOBALS['wgHooks']['PersonalUrls'][] = function (&$personal_urls, &$wgTitle) {
		unset( $personal_urls["login"] );
		unset( $personal_urls["logout"] );
		unset( $personal_urls['anonlogin'] );
		return true;
	}; #}

}

// don't let nobody do no account creatin'
// $wgGroupPermissions['*']['createaccount'] = false;
// $wgGroupPermissions['user']['createaccount'] = false;
// $wgGroupPermissions['sysop']['createaccount'] = false;
// $wgGroupPermissions['bureaucrat']['createaccount'] = false;
foreach( $wgGroupPermissions as $groupName => $perms ) {
	$wgGroupPermissions[ $groupName ]['createaccount'] = false;
}

// Allow auto-creation by SAML extension
$wgGroupPermissions['*']['autocreateaccount'] = true;
